The Scripting app provides a gesture system similar to SwiftUI, allowing any view (<VStack>, <HStack>, <Text>, etc.) to respond to touch interactions such as tapping, long pressing, dragging, rotating, and magnifying.
Dedicated keyboard surfaces can also use onTouchGesture on iOS 18+ for per-finger UIKit phases. This separate API does not accept GestureMask or change existing DragGesture. Read its cancellation, velocity/prediction and multitouch migration rules first.
You can use:
- Simplified gesture properties like
onTapGesture, onLongPressGesture, onDragGesture, or
- Advanced gesture objects such as
TapGesture(), LongPressGesture(), and the gesture modifiers for composition and priority control.
1. Simple Gesture Properties
These are convenient, shorthand ways to add gestures directly to a view.
onTapGesture
Executes an action when a tap gesture is recognized.
Type
onTapGesture?: (() => void) | {
count: number
perform: () => void
}
Parameters
Examples
// Single tap
<VStack onTapGesture={() => console.log('Tapped')} />
// Double tap
<HStack
onTapGesture={{
count: 2,
perform: () => console.log('Double tapped')
}}
/>
onLongPressGesture
Executes an action when a long press gesture is recognized.
Type
onLongPressGesture?: (() => void) | {
minDuration?: number
maxDuration?: number
perform: () => void
onPressingChanged?: (state: boolean) => void
}
Parameters
Examples
// Simple usage
<VStack onLongPressGesture={() => console.log('Long pressed')} />
// Custom duration and press state callback
<HStack
onLongPressGesture={{
minDuration: 800,
maxDuration: 3000,
perform: () => console.log('Long press success'),
onPressingChanged: isPressing =>
console.log(isPressing ? 'Pressing...' : 'Released')
}}
/>
onDragGesture
Adds a drag gesture to a view, tracking position, offset, and velocity.
Type
onDragGesture?: {
minDistance?: number
coordinateSpace?: 'local' | 'global'
onChanged?: (details: DragGestureDetails) => void
onEnded?: (details: DragGestureDetails) => void
}
Parameters
DragGestureDetails
type DragGestureDetails = {
time: number
location: Point
startLocation: Point
translation: Size
velocity: Size
predictedEndLocation: Point
predictedEndTranslation: Size
}
Example
<VStack
onDragGesture={{
minDistance: 5,
coordinateSpace: 'global',
onChanged: details => {
console.log('Location:', details.location)
console.log('Offset:', details.translation)
},
onEnded: details => {
console.log('Predicted end:', details.predictedEndLocation)
}
}}
/>
2. Gesture Classes (Advanced Usage)
For complex interaction handling or gesture composition, use the gesture constructors and modifiers.
GestureInfo Class
All gesture constructors return a GestureInfo object that defines configuration and callbacks.
class GestureInfo<Options, Value> {
type: string
options: Options
onChanged(callback: (value: Value) => void): this
onEnded(callback: (value: Value) => void): this
}
Example
<Text
gesture={
TapGesture()
.onEnded(() => console.log('Tapped'))
}
/>
TapGesture
Detects single or multiple taps.
declare function TapGesture(count?: number): GestureInfo<number | undefined, void>
Example
<Text
gesture={
TapGesture(2)
.onEnded(() => console.log('Double tapped'))
}
/>
LongPressGesture
Detects press and hold gestures.
declare function LongPressGesture(options?: LongPressGestureOptions): GestureInfo<LongPressGestureOptions, boolean>
type LongPressGestureOptions = {
minDuration?: number
maxDuration?: number
}
Example
<Text
gesture={
LongPressGesture({ minDuration: 800 })
.onChanged(() => console.log('Pressing...'))
.onEnded(() => console.log('Long press finished'))
}
/>
DragGesture
Tracks finger or pointer movement.
declare function DragGesture(options?: DragGestureOptions): GestureInfo<DragGestureOptions, DragGestureDetails>
type DragGestureOptions = {
minDistance?: number
coordinateSpace?: 'local' | 'global'
}
Example
<VStack
gesture={
DragGesture({ coordinateSpace: 'global' })
.onChanged(d => console.log('Offset', d.translation))
.onEnded(d => console.log('Velocity', d.velocity))
}
/>
MagnifyGesture
Detects pinch zoom gestures.
declare function MagnifyGesture(minScaleDelta?: number | null): GestureInfo<number | null | undefined, MagnifyGestureValue>
type MagnifyGestureValue = {
time: Date
magnification: number
startAnchor: Point
startLocation: Point
velocity: number
}
Example
<Text
gesture={
MagnifyGesture(0.05)
.onChanged(v => console.log('Scale', v.magnification))
.onEnded(() => console.log('Zoom ended'))
}
/>
RotateGesture
Detects rotation gestures.
declare function RotateGesture(minAngleDelta?: Angle | null): GestureInfo<Angle | null | undefined, RotateGestureValue>
type RotateGestureValue = {
rotation: AngleValue
velocity: AngleValue
startAnchor: Point
time: Date
}
type AngleValue = {
radians: number
degrees: number
magnitude: number
animatableData: number
}
Example
<ZStack
gesture={
RotateGesture()
.onChanged(v => console.log('Rotation', v.rotation.degrees))
.onEnded(() => console.log('Rotation ended'))
}
/>
3. Gesture Modifiers for Views
All views support the following gesture-related properties.
type GesturesProps = {
gesture?: GestureProps
simultaneousGesture?: GestureProps
highPriorityGesture?: GestureProps
defersSystemGestures?: EdgeSet
}
gesture
Adds a gesture to the view.
<Text
gesture={
TapGesture()
.onEnded(() => console.log('Tapped'))
}
/>
highPriorityGesture
Adds a gesture with higher priority than existing ones on the view.
<Text
highPriorityGesture={
TapGesture(2)
.onEnded(() => console.log('Double tap takes priority'))
}
/>
simultaneousGesture
Allows multiple gestures to be recognized simultaneously.
<Text
simultaneousGesture={
LongPressGesture()
.onEnded(() => console.log('Long pressed'))
}
gesture={
TapGesture()
.onEnded(() => console.log('Tapped'))
}
/>
Gives your custom gestures precedence over system gestures originating from screen edges.
<VStack defersSystemGestures="all">
<Text>Custom gestures take precedence</Text>
</VStack>
Accepted Values
4. GestureMask
Controls how adding a gesture affects other gestures on the same view or its subviews.
type GestureMask = "all" | "gesture" | "subviews" | "none"
Example
<VStack
gesture={{
gesture: TapGesture().onEnded(() => console.log('Tapped')),
mask: 'gesture'
}}
>
<Text>Tap here</Text>
</VStack>
5. Summary Table
GestureInputKinds accepts "all" or an array of "directTouch", "indirectTouch", "pencil", and "pointer". Omitted means all sources; an empty array accepts none. Earlier systems ignore the filter and preserve existing gesture behavior.
Set inputKinds in the object forms of onTapGesture, onLongPressGesture, and onDragGesture. The object form of onTapGesture.perform receives { x, y } in the requested coordinate space; the function shorthand still takes no arguments. count defaults to 1.
TapGesture accepts its existing numeric count or { count?, inputKinds? }. DragGesture and LongPressGesture accept inputKinds in their existing options. RotateGesture(minAngleDelta?, inputKinds?) and MagnifyGesture(minScaleDelta?, inputKinds?) add an optional second parameter and retain the old calling forms. The filter also applies when these gesture objects are used with gesture, simultaneousGesture, or highPriorityGesture.
<Text onTapGesture={{ inputKinds: ["pointer"], perform: point => console.log(point) }}>Tap</Text>
<Text gesture={TapGesture({ count: 2, inputKinds: ["directTouch"] }).onEnded(() => {})}>Double tap</Text>
<Text gesture={MagnifyGesture(0.01, ["directTouch"])}>Zoom</Text>
type GestureInputKind = "directTouch" | "indirectTouch" | "pencil" | "pointer"
type GestureInputKinds = "all" | GestureInputKind[]